veyrix

Veyrix IDE is a lightweight, p...
Log | Files | Refs | README | LICENSE

index copy.html (111970B)


      1 <!DOCTYPE html>
      2 <html lang="en" class="dark">
      3 <head>
      4     <meta charset="UTF-8">
      5     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
      6     <title>Veyrix IDE</title>
      7     
      8     <link rel="manifest" href="assets/manifest.json">
      9     <link rel="icon" type="image/svg+xml" href="assets/veyrix.svg">
     10     <meta name="theme-color" content="#121b2a">
     11     
     12     <!-- Tailwind CSS -->
     13     <script src="https://cdn.tailwindcss.com"></script>
     14     <script>
     15         tailwind.config = {
     16             darkMode: 'class',
     17             theme: {
     18                 extend: {
     19                     colors: {
     20                         veyrix: {
     21                             bg: '#0a0f1a',
     22                             surface: '#121b2a',
     23                             border: '#1e293b',
     24                             accent: '#3b82f6',
     25                             accentHover: '#2563eb',
     26                             text: '#e2e8f0',
     27                             muted: '#94a3b8'
     28                         }
     29                     },
     30                     transitionTimingFunction: {
     31                         'material': 'cubic-bezier(0.2, 0.0, 0, 1.0)',
     32                     }
     33                 }
     34             }
     35         }
     36     </script>
     37     
     38     <style>
     39         /* Global tap highlight & focus reset */
     40         * {
     41             -webkit-tap-highlight-color: transparent;
     42         }
     43         
     44         :focus:not(:focus-visible) {
     45             outline: none;
     46         }
     47 
     48         :focus-visible {
     49             outline: 2px solid theme('colors.veyrix.accent');
     50             outline-offset: 1px;
     51             border-radius: 2px;
     52         }
     53 
     54         /* Custom UI Scrollbar & Base Styles */
     55         ::-webkit-scrollbar { width: 8px; height: 8px; }
     56         ::-webkit-scrollbar-track { background: transparent; }
     57         ::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
     58         ::-webkit-scrollbar-thumb:hover { background: #475569; }
     59         
     60         body {
     61             background-color: theme('colors.veyrix.bg');
     62             color: theme('colors.veyrix.text');
     63             overflow: hidden;
     64             touch-action: none;
     65         }
     66         
     67         .glass-panel {
     68             background: theme('colors.veyrix.surface');
     69             border: 1px solid theme('colors.veyrix.border');
     70         }
     71 
     72         /* --- SIDEBAR RESPONSIVE LOGIC --- */
     73         #sidebar {
     74             width: 280px;
     75             background-color: theme('colors.veyrix.surface');
     76             border-right: 1px solid theme('colors.veyrix.border');
     77             transition: width 0.25s ease, transform 0.25s ease;
     78             overflow: hidden;
     79             flex-shrink: 0;
     80             z-index: 40;
     81             display: flex;
     82             flex-direction: column;
     83         }
     84         
     85         #sidebar.sidebar-collapsed {
     86             width: 0;
     87             border-right-color: transparent;
     88         }
     89 
     90         @media (max-width: 768px) {
     91             #sidebar {
     92                 position: absolute;
     93                 top: 0;
     94                 bottom: 0;
     95                 left: 0;
     96                 transform: translateX(0);
     97                 box-shadow: 5px 0 25px rgba(0,0,0,0.7);
     98                 border-right: none;
     99             }
    100             #sidebar.sidebar-collapsed {
    101                 width: 280px;
    102                 transform: translateX(-100%);
    103             }
    104         }
    105         
    106         #editor {
    107             position: absolute;
    108             top: 0;
    109             right: 0;
    110             bottom: 0;
    111             left: 0;
    112             border-radius: 0.5rem;
    113             background: theme('colors.veyrix.surface');
    114         }
    115 
    116         .ace_editor { font-family: 'Fira Code', 'Consolas', monospace !important; }
    117         .ace-veyrix-dark {
    118             background-color: theme('colors.veyrix.surface');
    119             color: #d4d4d4;
    120         }
    121         
    122         .modal-enter { animation: modalIn 0.25s cubic-bezier(0.2, 0, 0, 1) forwards; }
    123         .modal-exit { animation: modalOut 0.2s cubic-bezier(0.2, 0, 0, 1) forwards; }
    124         
    125         @keyframes modalIn {
    126             from { opacity: 0; transform: scale(0.95) translateY(10px); }
    127             to { opacity: 1; transform: scale(1) translateY(0); }
    128         }
    129         @keyframes modalOut {
    130             from { opacity: 1; transform: scale(1) translateY(0); }
    131             to { opacity: 0; transform: scale(0.95) translateY(10px); }
    132         }
    133 
    134         /* Jon Suh Hamburger - Spin */
    135         .hamburger {
    136             display: inline-flex;
    137             align-items: center;
    138             justify-content: center;
    139             cursor: pointer;
    140             transition-property: opacity, filter;
    141             transition-duration: 0.15s;
    142             transition-timing-function: linear;
    143             border: 0;
    144             margin: 0;
    145             overflow: visible;
    146             background: transparent;
    147         }
    148         .hamburger:hover { opacity: 0.8; }
    149         .hamburger-box {
    150             width: 20px;
    151             height: 14px;
    152             display: inline-block;
    153             position: relative;
    154         }
    155         .hamburger-inner {
    156             display: block;
    157             top: 50%;
    158             margin-top: -1px;
    159         }
    160         .hamburger-inner, .hamburger-inner::before, .hamburger-inner::after {
    161             width: 20px;
    162             height: 2px;
    163             background-color: white;
    164             border-radius: 4px;
    165             position: absolute;
    166             transition-property: transform;
    167             transition-duration: 0.15s;
    168             transition-timing-function: ease;
    169         }
    170         .hamburger:hover .hamburger-inner, .hamburger:hover .hamburger-inner::before, .hamburger:hover .hamburger-inner::after {
    171             background-color: white;
    172         }
    173         .hamburger.is-active .hamburger-inner, .hamburger.is-active .hamburger-inner::before, .hamburger.is-active .hamburger-inner::after {
    174             background-color: white;
    175         }
    176         .hamburger-inner::before, .hamburger-inner::after { content: ""; display: block; }
    177         .hamburger-inner::before { top: -6px; }
    178         .hamburger-inner::after { bottom: -6px; }
    179 
    180         .hamburger--spin .hamburger-inner { transition-duration: 0.22s; transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); }
    181         .hamburger--spin .hamburger-inner::before { transition: top 0.1s 0.25s ease-in, opacity 0.1s ease-in; }
    182         .hamburger--spin .hamburger-inner::after { transition: bottom 0.1s 0.25s ease-in, transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19); }
    183 
    184         .hamburger--spin.is-active .hamburger-inner { transform: rotate(225deg); transition-delay: 0.12s; transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
    185         .hamburger--spin.is-active .hamburger-inner::before { top: 0; opacity: 0; transition: top 0.1s ease-out, opacity 0.1s 0.12s ease-out; }
    186         .hamburger--spin.is-active .hamburger-inner::after { bottom: 0; transform: rotate(-90deg); transition: bottom 0.1s ease-out, transform 0.22s 0.12s cubic-bezier(0.215, 0.61, 0.355, 1); }
    187     </style>
    188 
    189     <!-- Dependencies -->
    190     <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ace.js"></script>
    191     <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ext-beautify.js"></script>
    192     <script src="https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js"></script>
    193 </head>
    194 <body class="flex flex-col text-sm antialiased selection:bg-veyrix-accent/30">
    195 
    196     <!-- Top Navigation Bar -->
    197     <header class="h-12 border-b border-veyrix-border bg-veyrix-surface flex items-center justify-between px-2 md:px-3 flex-shrink-0 z-40 relative gap-2">
    198         
    199         <!-- Left Side: Menu + Logo -->
    200         <div class="flex items-center gap-2 md:gap-3 shrink-0">
    201             <button class="hamburger hamburger--spin sidebar-toggle-btn p-1.5 rounded transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent" type="button" aria-expanded="false" aria-controls="sidebar" aria-label="Toggle menu" onclick="UI.toggleSidebar()">
    202                 <span class="hamburger-box">
    203                     <span class="hamburger-inner"></span>
    204                 </span>
    205             </button>
    206             
    207             <!-- Desktop Logo -->
    208             <div class="hidden md:flex items-center gap-2">
    209                 <img src="assets/veyrix.svg" alt="Veyrix Logo" class="w-6 h-6">
    210                 <span class="font-semibold text-white tracking-wide">Veyrix IDE</span>
    211             </div>
    212             
    213             <!-- Mobile Logo (Stacked) -->
    214             <div class="flex flex-col items-center justify-center md:hidden leading-[1.1] ml-0.5">
    215                 <img src="assets/veyrix.svg" alt="Veyrix Logo" class="w-[18px] h-[18px]">
    216                 <span class="font-bold text-white tracking-wider text-[8px] uppercase mt-[1px]">Veyrix</span>
    217             </div>
    218             
    219             <div class="h-5 w-px bg-veyrix-border mx-2 hidden md:block"></div>
    220 
    221         </div>
    222 
    223         <!-- Center: Active File Display (Dynamic Width) -->
    224         <div class="flex-1 flex justify-start md:justify-center min-w-0 overflow-hidden">
    225             <div id="top-active-file" class="hidden items-center gap-1.5 bg-black/30 px-3 py-1 rounded-full border border-white/10 max-w-full overflow-hidden whitespace-nowrap text-veyrix-text font-mono text-xs">
    226                 <span id="top-file-name" class="truncate">No file open</span>
    227                 <span id="top-file-unsaved" class="w-2 h-2 rounded-full bg-veyrix-accent hidden shrink-0"></span>
    228             </div>
    229         </div>
    230 
    231         <!-- Right Side: Action Buttons -->
    232         <div class="flex items-center gap-0.5 md:gap-1 shrink-0">
    233             <button onclick="document.getElementById('file-upload').click()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Open File (Ctrl+O)">
    234                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z"></path></svg>
    235             </button>
    236             <button onclick="App.saveCurrentFile()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Save (Ctrl+S)">
    237                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg>
    238             </button>
    239             <button onclick="App.triggerDownload()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Download File">
    240                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
    241             </button>
    242             <button onclick="App.triggerSnapshot()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Save Snapshot">
    243                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path></svg>
    244             </button>
    245             <button onclick="UI.showHistory()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="View History">
    246                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
    247             </button>
    248             <button onclick="UI.showModal('share-modal')" class="p-1 md:hidden rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Share">
    249                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg>
    250             </button>
    251             <button onclick="UI.showModal('share-modal')" class="hidden md:flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-veyrix-accent/10 text-veyrix-accent hover:bg-veyrix-accent/20 transition-colors border border-veyrix-accent/20 md:ml-1">
    252                 <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg>
    253                 Share
    254             </button>
    255             <button onclick="UI.showModal('help-modal')" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger md:ml-1" title="Help & Shortcuts">
    256                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
    257             </button>
    258             <button onclick="App.toggleFullscreen()" class="hidden md:block p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Fullscreen (F11)">
    259                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg>
    260             </button>
    261             <button onclick="UI.showModal('settings-modal')" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Settings">
    262                 <svg class="w-4 h-4 md:w-4 md:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
    263             </button>
    264         </div>
    265     </header>
    266 
    267     <!-- Main Workspace -->
    268     <main class="flex-1 flex overflow-hidden relative z-10 w-full" id="main-workspace">
    269         
    270         <!-- Mobile Sidebar Backdrop -->
    271         <div id="sidebar-backdrop" class="fixed inset-0 bg-black/60 z-30 hidden md:hidden transition-opacity duration-250 opacity-0"></div>
    272 
    273         <!-- Sidebar / File Explorer -->
    274         <aside id="sidebar" class="sidebar-collapsed bg-veyrix-surface">
    275             <div class="p-3 border-b border-veyrix-border flex items-center justify-between">
    276                 <span class="text-xs font-bold text-veyrix-muted uppercase tracking-wider">Explorer</span>
    277                 <div class="flex gap-1">
    278                     <button onclick="UI.showModal('new-file-modal')" class="p-1 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors" title="New File">
    279                         <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
    280                     </button>
    281                 </div>
    282             </div>
    283             <div id="file-list" class="flex-1 overflow-y-auto p-2 space-y-0.5 relative">
    284                 <!-- File items injected here -->
    285             </div>
    286             
    287             <!-- Mobile Sidebar Footer -->
    288             <div class="md:hidden p-3 border-t border-veyrix-border flex justify-between">
    289                 <button onclick="UI.toggleSidebar(true)" class="text-xs text-veyrix-muted hover:text-white flex items-center gap-1 w-full justify-center bg-white/5 hover:bg-white/10 py-2 rounded-lg transition-colors">
    290                     <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
    291                     Close Sidebar
    292                 </button>
    293             </div>
    294         </aside>
    295 
    296         <!-- Editor Area -->
    297         <section id="editor-section" class="flex-1 relative p-2 md:p-4 bg-veyrix-bg flex flex-col min-w-0">
    298             <!-- Security/Share Warning Banner -->
    299             <div id="banner-warning" class="hidden mb-2 bg-amber-500/10 border border-amber-500/20 text-amber-200 text-xs px-3 py-2 rounded-lg flex items-center justify-between">
    300                 <div class="flex items-center gap-2">
    301                     <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
    302                     <span id="banner-text">Viewing shared file. Edits won't sync automatically.</span>
    303                 </div>
    304                 <button onclick="this.parentElement.classList.add('hidden')" class="hover:text-amber-100">×</button>
    305             </div>
    306             
    307             <div class="flex-1 relative rounded-lg overflow-hidden border border-veyrix-border shadow-2xl glass-panel">
    308                 <div id="editor-placeholder" class="absolute inset-0 flex flex-col items-center justify-center text-veyrix-muted bg-veyrix-surface z-10">
    309                     <img src="assets/veyrix.svg" alt="Veyrix" class="w-20 h-20 mb-6 opacity-20 grayscale">
    310                     <p class="text-sm font-medium text-veyrix-text">Ready to build something?</p>
    311                     <div class="mt-6 flex gap-4 text-xs text-veyrix-muted">
    312                         <button onclick="UI.showModal('new-file-modal')" class="px-3 py-1.5 rounded-md bg-white/5 hover:bg-white/10 border border-white/10 text-veyrix-text transition-colors">Create New File</button>
    313                     </div>
    314                     <div id="app-version-display" class="absolute bottom-4 text-[10px] font-mono text-veyrix-muted/30 select-none"></div>
    315                 </div>
    316                 <div id="editor"></div>
    317             </div>
    318         </section>
    319 
    320         <!-- Floating Action Button for Mobile -->
    321         <button onclick="UI.showModal('new-file-modal')" class="md:hidden fixed bottom-6 right-6 w-14 h-14 bg-veyrix-accent hover:bg-veyrix-accentHover rounded-full shadow-lg flex items-center justify-center text-white z-20 transition-transform active:scale-95">
    322             <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
    323         </button>
    324     </main>
    325 
    326     <!-- Global Dropdown Menu (Z-index top priority) -->
    327     <div id="global-file-dropdown" class="hidden absolute w-40 bg-slate-800 border border-slate-700 rounded-lg shadow-xl z-[100] flex-col py-1 transition-opacity duration-150 opacity-0 scale-95 origin-top-right">
    328         <button id="dd-rename" class="flex items-center gap-2 w-full text-left px-3 py-2 text-xs text-slate-200 hover:bg-slate-700 hover:text-white transition-colors">
    329             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path></svg> Rename
    330         </button>
    331         <button id="dd-download" class="flex items-center gap-2 w-full text-left px-3 py-2 text-xs text-slate-200 hover:bg-slate-700 hover:text-white transition-colors">
    332             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg> Download
    333         </button>
    334         <button id="dd-share" class="flex items-center gap-2 w-full text-left px-3 py-2 text-xs text-slate-200 hover:bg-slate-700 hover:text-white transition-colors">
    335             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg> Share
    336         </button>
    337         <div class="h-px bg-slate-700 my-1 mx-2"></div>
    338         <button id="dd-delete" class="flex items-center gap-2 w-full text-left px-3 py-2 text-xs text-red-400 hover:bg-slate-700 hover:text-red-300 transition-colors">
    339             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg> Delete
    340         </button>
    341     </div>
    342 
    343     <!-- Hidden File Input for Open -->
    344     <input type="file" id="file-upload" class="hidden" accept=".txt,.js,.html,.css,.json,.md,.xml,.csv,.log,.sh,.py,.yml,.yaml,.ini,.env,text/*" onchange="App.handleFileUpload(event)">
    345 
    346     <!-- TOAST CONTAINER -->
    347     <div id="toast-container" class="fixed bottom-16 md:bottom-6 right-4 md:right-6 z-50 flex flex-col gap-2 pointer-events-none"></div>
    348 
    349     <!-- MODALS OVERLAY -->
    350     <div id="modal-overlay" class="fixed inset-0 bg-black/60 z-50 hidden items-start pt-16 md:pt-0 md:items-center justify-center p-4 overflow-y-auto">
    351         
    352         <!-- Generic Confirm Modal -->
    353         <div id="confirm-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    354             <div class="p-5 space-y-4 text-center">
    355                 <div class="w-12 h-12 rounded-full bg-veyrix-accent/10 text-veyrix-accent flex items-center justify-center mx-auto mb-2" id="confirm-icon-bg">
    356                     <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
    357                 </div>
    358                 <h3 class="font-semibold text-white" id="confirm-title">Confirm Action</h3>
    359                 <p class="text-xs text-veyrix-muted" id="confirm-message">Are you sure?</p>
    360                 <div class="flex gap-2 justify-center pt-2">
    361                     <button onclick="UI.hideModal()" class="flex-1 py-2 bg-white/5 hover:bg-white/10 text-white rounded-lg text-sm transition-colors">Cancel</button>
    362                     <button id="confirm-action-btn" class="flex-1 py-2 bg-veyrix-accent hover:bg-veyrix-accentHover text-white rounded-lg text-sm font-medium transition-colors">Confirm</button>
    363                 </div>
    364             </div>
    365         </div>
    366 
    367         <!-- Snapshot Limit Modal -->
    368         <div id="snapshot-limit-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    369             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg">
    370                 <h3 class="font-semibold text-white">Snapshot Limit Reached</h3>
    371                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    372             </div>
    373             <div class="p-5 space-y-4">
    374                 <p class="text-sm text-veyrix-text">You have reached the maximum limit of 5 snapshots for this file.</p>
    375                 <div class="bg-white/5 border border-white/10 rounded-lg p-3">
    376                     <p class="text-xs text-veyrix-muted mb-1">Oldest Snapshot:</p>
    377                     <p class="text-sm font-mono text-white" id="snapshot-limit-oldest-info"></p>
    378                 </div>
    379                 <p class="text-xs text-amber-400">Creating a new snapshot will permanently delete the oldest one.</p>
    380             </div>
    381             <div class="p-4 border-t border-veyrix-border bg-black/20 flex justify-end gap-2">
    382                 <button onclick="UI.hideModal()" class="px-4 py-2 text-sm rounded-lg hover:bg-white/5 text-veyrix-text transition-colors">Cancel</button>
    383                 <button onclick="App.executeSnapshotSave(true)" class="px-4 py-2 text-sm rounded-lg bg-veyrix-accent text-white hover:bg-veyrix-accentHover transition-colors font-medium">Delete Oldest & Continue</button>
    384             </div>
    385         </div>
    386 
    387         <!-- Help & Shortcuts Modal -->
    388         <div id="help-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-md shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0 max-h-[90vh]">
    389             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg shrink-0">
    390                 <h3 class="font-semibold text-white flex items-center gap-2">
    391                     <svg class="w-5 h-5 text-veyrix-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
    392                     Help & Shortcuts
    393                 </h3>
    394                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    395             </div>
    396             <div class="p-5 overflow-y-auto space-y-6">
    397                 <div class="bg-veyrix-accent/10 border border-veyrix-accent/20 rounded-lg p-3 text-sm text-veyrix-accent">
    398                 <div class="flex flex-wrap items-center gap-x-2 leading-relaxed">
    399                     <svg class="w-4 h-4 opacity-80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    400                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
    401                     </svg>
    402 
    403                     <span class="font-medium">Recommendation:</span>
    404                     
    405                     <span class="flex items-center gap-1.5">
    406                     Use <strong>Fullscreen</strong> 
    407                     <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    408                         <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path>
    409                     </svg>
    410                     </span>
    411 
    412                     <span>or press</span>
    413 
    414                     <kbd class="px-2 py-0.5 text-[11px] font-bold bg-veyrix-accent/25 border-b-2 border-veyrix-accent/40 rounded text-veyrix-accent">
    415                     F11
    416                     </kbd>
    417 
    418                     <span class="opacity-90">for the best IDE experience.</span>
    419                     
    420                     
    421                 </div>
    422                 </div>
    423                 <div class="bg-veyrix-accent/10 border border-veyrix-accent/20 rounded-lg p-3 text-sm text-veyrix-accent leading-relaxed">
    424   <!-- Info Icon - inline-block keeps it in the flow -->
    425   <svg class="w-4 h-4 inline-block mr-1.5 opacity-80 -mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    426     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
    427   </svg>
    428 
    429   <span>Install Veyrix IDE on your device for a faster experience with offline capabilities.<br>Go to</span>
    430 
    431   <!-- Action Group: Locked together so they never break apart -->
    432   <span class="inline-flex items-center gap-1 ml-1">
    433     <svg class="w-3.5 h-3.5 opacity-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    434       <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
    435       <circle cx="12" cy="12" r="3" stroke-width="2"></circle>
    436     </svg>
    437     <span class="mx-0.5 opacity-80">→</span>
    438     <strong class="font-bold">Install Web App</strong>
    439   </span>
    440 </div>
    441 
    442                 <section>
    443                     <h4 class="text-sm font-semibold text-white border-b border-veyrix-border pb-1 mb-3">Icon Guide & Usage</h4>
    444                     <ul class="space-y-4 text-xs text-veyrix-muted">
    445                         <li class="flex items-start gap-3">
    446                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z"></path></svg></div>
    447                             <div><strong class="text-white block mb-0.5">Open File</strong> Load a text or code file from your device into the IDE.</div>
    448                         </li>
    449                         <li class="flex items-start gap-3">
    450                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg></div>
    451                             <div><strong class="text-white block mb-0.5">Save</strong> Manually save the current file to your browser's local storage.</div>
    452                         </li>
    453                         <li class="flex items-start gap-3">
    454                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg></div>
    455                             <div><strong class="text-white block mb-0.5">Download</strong> Download the file to your device.</div>
    456                         </li>
    457                         <li class="flex items-start gap-3">
    458                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path></svg></div>
    459                             <div><strong class="text-white block mb-0.5">Save Snapshot</strong> Create a versioned backup of your current file (maximum 5 snapshots per file).</div>
    460                         </li>
    461                         <li class="flex items-start gap-3">
    462                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg></div>
    463                             <div><strong class="text-white block mb-0.5">View History</strong> View all your saved snapshots and safely restore older versions.</div>
    464                         </li>
    465                         <li class="flex items-start gap-3">
    466                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg></div>
    467                             <div><strong class="text-white block mb-0.5">Share</strong> Share your code directly via device, fast local link, or cloud upload.</div>
    468                         </li>
    469                     </ul>
    470                 </section>
    471 
    472                 <section>
    473                     <h4 class="text-sm font-semibold text-white border-b border-veyrix-border pb-1 mb-2">Keyboard Shortcuts</h4>
    474                     <div class="grid grid-cols-2 gap-2 text-xs">
    475                         <div class="text-veyrix-muted flex justify-between pr-2 border-r border-white/5"><span>Save File</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">Ctrl+S</kbd></div>
    476                         <div class="text-veyrix-muted flex justify-between pl-2"><span>Open File</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">Ctrl+O</kbd></div>
    477                         <div class="text-veyrix-muted flex justify-between pr-2 border-r border-white/5"><span>Format Code</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">Alt+Shift+F</kbd></div>
    478                         <div class="text-veyrix-muted flex justify-between pl-2"><span>Fullscreen</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">F11</kbd></div>
    479                         <div class="text-veyrix-muted flex justify-between pr-2 border-r border-white/5"><span>Close Modal</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">Esc</kbd></div>
    480                     </div>
    481                 </section>
    482             </div>
    483         </div>
    484 
    485         <!-- Create New File Modal -->
    486         <div id="new-file-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    487             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg">
    488                 <h3 class="font-semibold text-white">Create New File</h3>
    489                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    490             </div>
    491             <div class="p-5 space-y-4">
    492                 <div>
    493                     <label class="block text-xs font-medium text-veyrix-muted mb-1.5">Filename</label>
    494                     <input type="text" id="new-file-input" placeholder="e.g., app.js, Dockerfile, .env" 
    495                            class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent transition-all">
    496                     <p class="text-[10px] text-veyrix-muted mt-1.5">No extension required. Recognized files will be auto-formatted.</p>
    497                 </div>
    498             </div>
    499             <div class="p-4 border-t border-veyrix-border bg-black/20 flex justify-end gap-2">
    500                 <button onclick="UI.hideModal()" class="px-4 py-2 text-sm rounded-lg hover:bg-white/5 text-veyrix-text transition-colors">Cancel</button>
    501                 <button onclick="App.createNewFile()" class="px-4 py-2 text-sm rounded-lg bg-veyrix-accent text-white hover:bg-veyrix-accentHover transition-colors font-medium">Create</button>
    502             </div>
    503         </div>
    504 
    505         <!-- Rename File Modal -->
    506         <div id="rename-file-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    507             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg">
    508                 <h3 class="font-semibold text-white">Rename File</h3>
    509                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    510             </div>
    511             <div class="p-5 space-y-4">
    512                 <div>
    513                     <label class="block text-xs font-medium text-veyrix-muted mb-1.5">New Filename</label>
    514                     <input type="text" id="rename-file-input" placeholder="e.g., app.js, Dockerfile, .env" 
    515                            class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent transition-all">
    516                 </div>
    517             </div>
    518             <div class="p-4 border-t border-veyrix-border bg-black/20 flex justify-end gap-2">
    519                 <button onclick="UI.hideModal()" class="px-4 py-2 text-sm rounded-lg hover:bg-white/5 text-veyrix-text transition-colors">Cancel</button>
    520                 <button onclick="App.executeRename()" class="px-4 py-2 text-sm rounded-lg bg-veyrix-accent text-white hover:bg-veyrix-accentHover transition-colors font-medium">Rename</button>
    521             </div>
    522         </div>
    523 
    524         <!-- Share Modal -->
    525         <div id="share-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-md shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0 max-h-[90vh]">
    526             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg shrink-0">
    527                 <h3 class="font-semibold text-white">Share File</h3>
    528                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    529             </div>
    530             
    531             <div class="flex border-b border-veyrix-border shrink-0">
    532                 <button onclick="UI.switchShareTab('local')" id="tab-btn-local" class="flex-1 py-3 text-sm font-medium border-b-2 border-veyrix-accent text-veyrix-accent transition-colors">Fast Local Share</button>
    533                 <button onclick="UI.switchShareTab('cloud')" id="tab-btn-cloud" class="flex-1 py-3 text-sm font-medium border-b-2 border-transparent text-veyrix-muted hover:text-white transition-colors">Cloud Share</button>
    534             </div>
    535 
    536             <div class="overflow-y-auto flex-1">
    537                 <div class="p-5">
    538                     <!-- Local Share Tab -->
    539                     <div id="tab-local" class="space-y-4">
    540                         <p class="text-xs text-veyrix-muted">Generates a URL containing the compressed code. No data is stored on servers. URL might be long.</p>
    541                         <button onclick="Cloud.generateLocalShare()" class="w-full py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-sm text-veyrix-text transition-colors flex items-center justify-center gap-2">
    542                             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path></svg>
    543                             Generate Local Link
    544                         </button>
    545                         <div id="local-share-result" class="hidden mt-3">
    546                             <input type="text" id="local-share-url" readonly class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-xs text-veyrix-muted focus:outline-none mb-2">
    547                             <button onclick="UI.copyToClipboard('local-share-url')" class="w-full py-2 bg-veyrix-accent/20 text-veyrix-accent rounded-lg text-sm font-medium hover:bg-veyrix-accent/30 transition-colors">Copy Link</button>
    548                         </div>
    549                     </div>
    550 
    551                     <!-- Cloud Share Tab -->
    552                     <div id="tab-cloud" class="space-y-4 hidden">
    553                         <div class="bg-blue-500/10 border border-blue-500/20 p-3 rounded-lg flex gap-3 text-sm">
    554                             <svg class="w-5 h-5 text-blue-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"></path></svg>
    555                             <p class="text-blue-200 text-xs">Saves to Firebase Firestore. Firebase initializes ONLY when you use this feature.</p>
    556                         </div>
    557                         
    558                         <div>
    559                             <label class="block text-xs font-medium text-veyrix-muted mb-1.5">Optional Password Protection</label>
    560                             <input type="password" id="cloud-password" placeholder="Leave blank for public access" 
    561                                 class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent transition-all">
    562                         </div>
    563 
    564                         <button onclick="Cloud.generateCloudShare()" id="btn-cloud-share" class="w-full py-2 bg-veyrix-accent hover:bg-veyrix-accentHover text-white rounded-lg text-sm font-medium transition-colors flex items-center justify-center gap-2">
    565                             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg>
    566                             Upload & Share
    567                         </button>
    568                         
    569                         <div id="cloud-share-result" class="hidden mt-3">
    570                             <input type="text" id="cloud-share-url" readonly class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-xs text-veyrix-muted focus:outline-none mb-2">
    571                             <button onclick="UI.copyToClipboard('cloud-share-url')" class="w-full py-2 bg-veyrix-accent/20 text-veyrix-accent rounded-lg text-sm font-medium hover:bg-veyrix-accent/30 transition-colors">Copy Link</button>
    572                         </div>
    573                     </div>
    574                 </div>
    575             </div>
    576 
    577             <!-- Native Web Share API Section -->
    578             <div id="native-share-section" class="hidden border-t border-veyrix-border bg-black/20 p-5 mt-auto shrink-0">
    579                 <h4 class="text-sm font-semibold text-white mb-3">Quick Share (Device Share)</h4>
    580                 <button onclick="Cloud.nativeShare()" class="w-full py-2 bg-veyrix-surface hover:bg-white/5 border border-white/10 rounded-lg text-sm text-veyrix-text transition-colors flex items-center justify-center gap-2">
    581                     <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg>
    582                     Share via Device
    583                 </button>
    584             </div>
    585         </div>
    586 
    587         <!-- Password Prompt Modal -->
    588         <div id="password-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    589             <div class="p-5 space-y-4 text-center">
    590                 <div class="w-12 h-12 rounded-full bg-amber-500/10 text-amber-500 flex items-center justify-center mx-auto mb-2">
    591                     <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
    592                 </div>
    593                 <h3 class="font-semibold text-white">Protected File</h3>
    594                 <p class="text-xs text-veyrix-muted">This shared file requires a password.</p>
    595                 <input type="password" id="access-password" placeholder="Enter password" 
    596                        class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-sm text-center text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent transition-all">
    597                 <button onclick="Cloud.verifyAndLoadCloudFile()" class="w-full py-2 bg-veyrix-accent text-white rounded-lg text-sm font-medium hover:bg-veyrix-accentHover transition-colors">Unlock</button>
    598             </div>
    599         </div>
    600 
    601         <!-- Settings & Data Security Modal -->
    602         <div id="settings-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-lg shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden max-h-[90vh] mb-auto md:mb-0 mt-4 md:mt-0">
    603             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg sticky top-0 z-10">
    604                 <h3 class="font-semibold text-white flex items-center gap-2">
    605                     <svg class="w-5 h-5 text-veyrix-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
    606                     Settings & Data Panel
    607                 </h3>
    608                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    609             </div>
    610             <div class="p-5 overflow-y-auto space-y-6">
    611                 
    612                 <!-- App Installation -->
    613                 <section class="space-y-3">
    614                     <h4 class="text-sm font-semibold text-white">App Installation</h4>
    615                     <div class="bg-white/5 border border-white/10 rounded-lg p-3 text-xs text-veyrix-muted space-y-2 flex flex-col sm:flex-row justify-between sm:items-center gap-3">
    616                         <p>Install Veyrix IDE on your device for a faster, app-like experience with offline capabilities.</p>
    617                         <button id="install-app-btn" onclick="App.installApp()" disabled class="shrink-0 px-4 py-2 bg-veyrix-accent text-white rounded-lg text-sm font-medium hover:bg-veyrix-accentHover transition-colors opacity-50 cursor-not-allowed">Install Web App</button>
    618                     </div>
    619                 </section>
    620 
    621                 <!-- Frequently Asked Questions -->
    622                 <section class="space-y-3">
    623                     <h4 class="text-sm font-semibold text-white">Frequently Asked Questions</h4>
    624                     <div class="space-y-2">
    625                         
    626                         <div class="border border-white/10 rounded-lg overflow-hidden">
    627                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    628                                 Where are my files stored?
    629                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    630                             </button>
    631                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    632                                 All your files are stored locally on your device inside your browser's secure IndexedDB. They are never uploaded to any server unless you specifically use the Cloud Share tool.
    633                             </div>
    634                         </div>
    635 
    636                         <div class="border border-white/10 rounded-lg overflow-hidden">
    637                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    638                                 What is Fast Share vs Cloud Share?
    639                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    640                             </button>
    641                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    642                                 <strong>Fast Share</strong> compresses your code using Lempel-Ziv and puts it entirely in the URL without using any server. <strong>Cloud Share</strong> uploads your code to an anonymous Firebase Firestore database, giving you a shorter, cleaner link.
    643                             </div>
    644                         </div>
    645 
    646                         <div class="border border-white/10 rounded-lg overflow-hidden">
    647                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    648                                 Is my data secure?
    649                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    650                             </button>
    651                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    652                                 Yes. Because files are local, no one can see them. If you use Cloud Share, you can add a password. Passwords are securely hashed using the Web Crypto API (SHA-256) locally before being checked against the database.
    653                             </div>
    654                         </div>
    655 
    656                         <div class="border border-white/10 rounded-lg overflow-hidden">
    657                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    658                                 How many snapshots can I take?
    659                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    660                             </button>
    661                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    662                                 You can save a maximum of 5 snapshots per file. If you try to save a 6th snapshot, you will be prompted to replace the oldest one.
    663                             </div>
    664                         </div>
    665 
    666                         <div class="border border-white/10 rounded-lg overflow-hidden">
    667                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    668                                 Does Veyrix IDE work offline?
    669                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    670                             </button>
    671                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    672                                 Yes! Once loaded for the first time, Veyrix operates entirely offline using PWA Service Workers. Note: Cloud Share requires an internet connection.
    673                             </div>
    674                         </div>
    675 
    676                         <div class="border border-white/10 rounded-lg overflow-hidden">
    677                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    678                                 Can I create custom file extensions?
    679                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    680                             </button>
    681                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    682                                 Absolutely. When creating a file, you can type any extension (e.g., <code>project.custom</code>) or no extension at all (e.g., <code>Dockerfile</code>). Unrecognized extensions will default to plain text mode formatting.
    683                             </div>
    684                         </div>
    685 
    686                     </div>
    687                 </section>
    688 
    689                 <!-- Extensions -->
    690                 <section class="space-y-3">
    691                     <h4 class="text-sm font-semibold text-white">Extensions</h4>
    692                     <div class="space-y-2">
    693                         <label class="flex items-center justify-between p-3 bg-white/5 border border-white/10 rounded-lg cursor-pointer hover:bg-white/10 transition-colors">
    694                             <div>
    695                                 <span class="text-sm text-white block">Prettier Code Formatter</span>
    696                                 <span class="text-[10px] text-veyrix-muted">Auto-formats on save (Alt+Shift+F)</span>
    697                             </div>
    698                             <div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
    699                                 <input type="checkbox" name="toggle" checked class="toggle-checkbox absolute block w-5 h-5 rounded-full bg-white border-4 appearance-none cursor-pointer border-veyrix-accent" style="right:0; border-color: #3b82f6;"/>
    700                                 <label class="toggle-label block overflow-hidden h-5 rounded-full bg-veyrix-accent cursor-pointer"></label>
    701                             </div>
    702                         </label>
    703                     </div>
    704                 </section>
    705 
    706                 <div class="border-t border-veyrix-border pt-4">
    707                     <button onclick="App.factoryReset()" class="w-full py-2 bg-red-500/10 text-red-400 border border-red-500/20 rounded-lg text-sm font-medium hover:bg-red-500/20 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500">Factory Reset (Delete All Data)</button>
    708                 </div>
    709 
    710                 <!-- Footer & Branding Section -->
    711                 <div class="border-t border-veyrix-border pt-6 pb-2 space-y-6">
    712                     <!-- Tagline & Version -->
    713                     <div class="text-center space-y-1">
    714                         <center><img src="assets/veyrix.svg" alt="Veyrix Logo" class="w-10 h-10"></center>
    715                         <h4 class="text-lg font-bold text-white tracking-wide">Veyrix</h4>
    716                         <p class="text-xs text-veyrix-muted font-light">Code. Anywhere.</p>
    717                         <p class="text-[10px] text-veyrix-muted/60 font-mono pt-1" id="settings-version-display"></p>
    718                     </div>
    719 
    720                     <!-- Buttons -->
    721                     <div class="flex flex-col sm:flex-row gap-3">
    722                         <a href="https://github.com/notamitgamer/veyrix" target="_blank" rel="noopener noreferrer" class="flex-1 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-sm text-veyrix-text transition-colors flex items-center justify-center gap-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent">
    723                             <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.161 22 16.416 22 12c0-5.523-4.477-10-10-10z"/></svg>
    724                             View Source on GitHub
    725                         </a>
    726                         <a href="/docs" class="flex-1 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-sm text-veyrix-text transition-colors flex items-center justify-center gap-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent">
    727                             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
    728                             Documentation
    729                         </a>
    730                     </div>
    731 
    732                     <!-- Creator & Disclaimer -->
    733                     <div class="text-center space-y-4">
    734                         <div class="text-xs text-veyrix-muted">
    735                             &copy; <a href="https://amit.is-a.dev" target="_blank" rel="noopener noreferrer" class="hover:text-veyrix-accent hover:underline transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent rounded px-0.5">Amit Dutta</a> 
    736                             <span class="mx-1 opacity-50">|</span> 
    737                             Username: <a href="https://github.com/notamitgamer" target="_blank" rel="noopener noreferrer" class="hover:text-veyrix-accent hover:underline transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent rounded px-0.5">notamitgamer</a>
    738                         </div>
    739                         
    740                         <div class="text-[10px] text-veyrix-muted/60 leading-relaxed bg-black/20 p-3 rounded-lg border border-white/5 mx-auto text-center">
    741                             Use of this tool is at your own risk.<br>
    742                             The creator is not responsible for any data loss, misuse, or damages caused by using this application.
    743                             The creator holds full rights to modify or permanently discontinue this project at any time without prior notice.
    744                         </div>
    745                     </div>
    746                 </div>
    747             </div>
    748         </div>
    749 
    750         <!-- History Modal -->
    751         <div id="history-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    752             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg">
    753                 <h3 class="font-semibold text-white">Version History</h3>
    754                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    755             </div>
    756             <div id="history-list" class="overflow-y-auto max-h-64 p-2 space-y-1">
    757                 <!-- Snapshots injected here -->
    758             </div>
    759         </div>
    760 
    761     </div>
    762 
    763 <script>
    764 /**
    765  * VEYRIX IDE - CORE APPLICATION LOGIC
    766  */
    767 
    768 const APP_VERSION = '1.0.6';
    769 
    770 // ===== FIREBASE CONFIGURATION =====
    771 // Firebase initializes ONLY when Cloud Share is selected.
    772 const firebaseConfig = {
    773 
    774 };
    775 
    776 // Application State
    777 const state = {
    778     files: [],
    779     activeFileId: null,
    780     editor: null,
    781     db: null,
    782     renameTargetId: null
    783 };
    784 
    785 // Utilities
    786 const Utils = {
    787     generateId: () => 'f_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36),
    788     getDisplayName: (file) => file.ext ? `${file.name}.${file.ext}` : file.name,
    789     getMobileShortName: (name, ext) => {
    790         let sName = name || '';
    791         let sExt = ext || '';
    792         
    793         if (sName.length > 3) {
    794             sName = sName.substring(0, 2) + '...';
    795         }
    796         if (sExt.length > 3) {
    797             sExt = sExt.substring(0, 2) + '...';
    798         }
    799         
    800         return sExt ? `${sName}.${sExt}` : sName;
    801     },
    802     getFileIcon: (ext, name = '') => {
    803         const fullName = ext ? `${name}.${ext}` : name;
    804         const lowerName = fullName.toLowerCase();
    805         const lowerExt = ext.toLowerCase();
    806 
    807         const baseClass = "w-4 h-4 shrink-0 ";
    808         if(lowerExt === 'html') return `<svg class="${baseClass} text-orange-500" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l2 15 8 2 8-2 2-15L12 2zm0 2.5l8 4-1.5 11.5-6.5 1.5-6.5-1.5L4 8.5l8-4z"/></svg>`;
    809         if(lowerExt === 'css') return `<svg class="${baseClass} text-blue-500" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l2 15 8 2 8-2 2-15L12 2zm0 2.5l8 4-1.5 11.5-6.5 1.5-6.5-1.5L4 8.5l8-4z"/></svg>`;
    810         if(lowerExt === 'js' || lowerExt === 'jsx' || lowerExt === 'ts' || lowerExt === 'tsx') return `<svg class="${baseClass} text-yellow-400" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l2 15 8 2 8-2 2-15L12 2zm0 2.5l8 4-1.5 11.5-6.5 1.5-6.5-1.5L4 8.5l8-4z"/></svg>`;
    811         if(lowerExt === 'json') return `<svg class="${baseClass} text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path></svg>`;
    812         if(lowerExt === 'md') return `<svg class="${baseClass} text-blue-300" fill="currentColor" viewBox="0 0 24 24"><path d="M20.56 18H3.44C2.65 18 2 17.37 2 16.59V7.41C2 6.63 2.65 6 3.44 6h17.12C21.35 6 22 6.63 22 7.41v9.18c0 .78-.65 1.41-1.44 1.41zM11 14l-2.5-3.5L6 14H4V9h2v2.5l2.5-3.5L11 11.5V9h2v5h-2zM20 12h-2V9h-2v3h-2l3 3 3-3z"/></svg>`;
    813         
    814         // Config and special files
    815         if(lowerName === 'dockerfile' || lowerName === 'makefile' || lowerName === '.env' || lowerExt === 'env' || lowerName.endsWith('gitignore') || lowerExt === 'yml' || lowerExt === 'yaml') {
    816             return `<svg class="${baseClass} text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>`;
    817         }
    818         
    819         return `<svg class="${baseClass} text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>`;
    820     },
    821     getModeFromExt: (ext, name = '') => {
    822         const fullName = ext ? `${name}.${ext}` : name;
    823         const lowerFullName = fullName.toLowerCase();
    824         const lowerExt = ext.toLowerCase();
    825 
    826         const map = { html: 'html', css: 'css', js: 'javascript', jsx: 'javascript', ts: 'javascript', tsx: 'javascript', json: 'json', md: 'markdown', txt: 'text', py: 'python', yml: 'yaml', yaml: 'yaml', xml: 'xml', sh: 'sh', csv: 'text' };
    827         
    828         if (map[lowerExt]) return map[lowerExt];
    829         if (lowerFullName === 'dockerfile') return 'dockerfile';
    830         if (lowerFullName === 'makefile') return 'makefile';
    831         if (lowerFullName.endsWith('.env') || lowerExt === 'env') return 'ini';
    832         if (lowerFullName.endsWith('gitignore')) return 'gitignore';
    833 
    834         return 'text';
    835     },
    836     debounce: (func, wait) => {
    837         let timeout;
    838         return function executedFunction(...args) {
    839             const later = () => { clearTimeout(timeout); func(...args); };
    840             clearTimeout(timeout);
    841             timeout = setTimeout(later, wait);
    842         };
    843     },
    844     async hashPassword(password) {
    845         if (!password) return null;
    846         const msgBuffer = new TextEncoder().encode(password);
    847         const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
    848         const hashArray = Array.from(new Uint8Array(hashBuffer));
    849         return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    850     }
    851 };
    852 
    853 // IndexedDB Wrapper
    854 const DB = {
    855     init: () => {
    856         return new Promise((resolve, reject) => {
    857             const request = indexedDB.open('VeyrixFS', 1);
    858             request.onerror = () => reject(request.error);
    859             request.onsuccess = () => { state.db = request.result; resolve(); };
    860             request.onupgradeneeded = (e) => {
    861                 const db = e.target.result;
    862                 if (!db.objectStoreNames.contains('files')) {
    863                     db.createObjectStore('files', { keyPath: 'id' });
    864                 }
    865             };
    866         });
    867     },
    868     getAll: () => {
    869         return new Promise((resolve, reject) => {
    870             const tx = state.db.transaction('files', 'readonly');
    871             const store = tx.objectStore('files');
    872             const request = store.getAll();
    873             request.onsuccess = () => resolve(request.result);
    874             request.onerror = () => reject(request.error);
    875         });
    876     },
    877     save: (file) => {
    878         return new Promise((resolve, reject) => {
    879             const tx = state.db.transaction('files', 'readwrite');
    880             const store = tx.objectStore('files');
    881             file.lastModified = Date.now();
    882             const request = store.put(file);
    883             request.onsuccess = () => resolve(file);
    884             request.onerror = () => reject(request.error);
    885         });
    886     },
    887     delete: (id) => {
    888         return new Promise((resolve, reject) => {
    889             const tx = state.db.transaction('files', 'readwrite');
    890             const store = tx.objectStore('files');
    891             const request = store.delete(id);
    892             request.onsuccess = () => resolve();
    893             request.onerror = () => reject(request.error);
    894         });
    895     }
    896 };
    897 
    898 // PWA Install Logic
    899 let deferredPrompt;
    900 window.addEventListener('beforeinstallprompt', (e) => {
    901     e.preventDefault();
    902     deferredPrompt = e;
    903     const btn = document.getElementById('install-app-btn');
    904     if(btn) {
    905         btn.classList.remove('opacity-50', 'cursor-not-allowed');
    906         btn.disabled = false;
    907     }
    908 });
    909 
    910 // UI Manager
    911 const UI = {
    912     activeModal: null,
    913     activeDropdownId: null,
    914 
    915     toggleSidebar: (forceClose = false) => {
    916         const sidebar = document.getElementById('sidebar');
    917         const backdrop = document.getElementById('sidebar-backdrop');
    918         const hamburger = document.querySelector('.hamburger');
    919         
    920         let isCollapsed = sidebar.classList.contains('sidebar-collapsed');
    921         if (forceClose) {
    922             isCollapsed = false; // Intention: want to collapse it
    923         }
    924 
    925         if (isCollapsed) {
    926             // Open Sidebar
    927             sidebar.classList.remove('sidebar-collapsed');
    928             localStorage.setItem('veyrix_sidebar', 'open');
    929             if (hamburger) {
    930                 hamburger.classList.add('is-active');
    931                 hamburger.setAttribute('aria-expanded', 'true');
    932             }
    933             if (window.innerWidth <= 768) {
    934                 backdrop.classList.remove('hidden');
    935                 requestAnimationFrame(() => backdrop.classList.remove('opacity-0'));
    936             }
    937         } else {
    938             // Close Sidebar
    939             sidebar.classList.add('sidebar-collapsed');
    940             localStorage.setItem('veyrix_sidebar', 'closed');
    941             if (hamburger) {
    942                 hamburger.classList.remove('is-active');
    943                 hamburger.setAttribute('aria-expanded', 'false');
    944             }
    945             backdrop.classList.add('opacity-0');
    946             setTimeout(() => backdrop.classList.add('hidden'), 250);
    947         }
    948         
    949         // Ensure Ace Editor resizes after DOM reflow transition
    950         setTimeout(() => { if (state.editor) state.editor.resize(); }, 260);
    951     },
    952 
    953     toggleFileDropdown: (id, btnElement, e) => {
    954         e.stopPropagation();
    955         const dd = document.getElementById('global-file-dropdown');
    956         
    957         if (UI.activeDropdownId === id && !dd.classList.contains('hidden')) {
    958             UI.closeAllDropdowns();
    959             return;
    960         }
    961         
    962         UI.activeDropdownId = id;
    963         const rect = btnElement.getBoundingClientRect();
    964         
    965         dd.classList.remove('hidden');
    966         dd.style.top = (rect.bottom + 4) + 'px';
    967         dd.style.left = (rect.right - 160) + 'px'; // width 40 = 160px
    968         
    969         // Map current actions to buttons
    970         document.getElementById('dd-rename').onclick = (ev) => { App.triggerRename(id, ev); };
    971         document.getElementById('dd-download').onclick = (ev) => { App.triggerDownload(id, ev); };
    972         document.getElementById('dd-share').onclick = (ev) => { App.triggerShare(id, ev); };
    973         document.getElementById('dd-delete').onclick = (ev) => { App.triggerDelete(id, ev); };
    974 
    975         requestAnimationFrame(() => {
    976             dd.classList.remove('opacity-0', 'scale-95');
    977             dd.classList.add('opacity-100', 'scale-100');
    978         });
    979     },
    980 
    981     closeAllDropdowns: () => {
    982         const dd = document.getElementById('global-file-dropdown');
    983         if(dd && !dd.classList.contains('hidden')) {
    984             dd.classList.remove('opacity-100', 'scale-100');
    985             dd.classList.add('opacity-0', 'scale-95');
    986             setTimeout(() => dd.classList.add('hidden'), 150);
    987             UI.activeDropdownId = null;
    988         }
    989     },
    990 
    991     showConfirm: (title, message, confirmText, actionCallback) => {
    992         document.getElementById('confirm-title').textContent = title;
    993         document.getElementById('confirm-message').textContent = message;
    994         
    995         const btn = document.getElementById('confirm-action-btn');
    996         btn.textContent = confirmText;
    997         
    998         // Handle danger vs normal confirm look
    999         const iconBg = document.getElementById('confirm-icon-bg');
   1000         if (confirmText.toLowerCase().includes('delete') || confirmText.toLowerCase().includes('restore')) {
   1001             btn.className = "flex-1 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-medium transition-colors";
   1002             iconBg.className = "w-12 h-12 rounded-full bg-red-500/10 text-red-500 flex items-center justify-center mx-auto mb-2";
   1003         } else {
   1004             btn.className = "flex-1 py-2 bg-veyrix-accent hover:bg-veyrix-accentHover text-white rounded-lg text-sm font-medium transition-colors";
   1005             iconBg.className = "w-12 h-12 rounded-full bg-veyrix-accent/10 text-veyrix-accent flex items-center justify-center mx-auto mb-2";
   1006         }
   1007 
   1008         btn.onclick = () => {
   1009             UI.hideModal();
   1010             actionCallback();
   1011         };
   1012         UI.showModal('confirm-modal');
   1013     },
   1014 
   1015     showHistory: () => {
   1016         const file = state.files.find(f => f.id === state.activeFileId);
   1017         if(!file) return UI.toast('No file open', 'error');
   1018         const list = document.getElementById('history-list');
   1019         list.innerHTML = '';
   1020         
   1021         if (!file.snapshots || file.snapshots.length === 0) {
   1022             list.innerHTML = `<p class="text-xs text-veyrix-muted p-3 text-center">No snapshots saved.</p>`;
   1023         } else {
   1024             [...file.snapshots].reverse().forEach((snap, idx) => {
   1025                 const date = new Date(snap.ts).toLocaleString();
   1026                 const realIndex = file.snapshots.length - 1 - idx;
   1027                 const versionNumber = snap.version || (realIndex + 1);
   1028                 
   1029                 const el = document.createElement('div');
   1030                 el.className = 'flex items-center justify-between p-3 hover:bg-white/5 rounded-md text-sm border-b border-veyrix-border/50';
   1031                 el.innerHTML = `
   1032                     <div class="flex flex-col">
   1033                         <span class="text-white">Version ${versionNumber}</span>
   1034                         <span class="text-xs text-veyrix-muted">${date}</span>
   1035                     </div>
   1036                     <button onclick="App.triggerRestore(${realIndex})" class="text-veyrix-accent hover:text-veyrix-accentHover text-xs font-medium px-3 py-1.5 rounded bg-veyrix-accent/10 hover:bg-veyrix-accent/20 transition-colors">Restore</button>
   1037                 `;
   1038                 list.appendChild(el);
   1039             });
   1040         }
   1041         UI.showModal('history-modal');
   1042     },
   1043 
   1044     showModal: (id) => {
   1045         if (UI.activeModal) UI.hideModal();
   1046         UI.closeAllDropdowns();
   1047         const overlay = document.getElementById('modal-overlay');
   1048         const modal = document.getElementById(id);
   1049         overlay.classList.remove('hidden');
   1050         overlay.classList.add('flex');
   1051         modal.classList.remove('hidden');
   1052         modal.classList.add('modal-enter');
   1053         modal.classList.remove('modal-exit');
   1054         UI.activeModal = modal;
   1055 
   1056         if(id === 'new-file-modal') {
   1057             setTimeout(() => {
   1058                 const input = document.getElementById('new-file-input');
   1059                 if (input) input.focus();
   1060             }, 100);
   1061         } else if(id === 'rename-file-modal') {
   1062             setTimeout(() => {
   1063                 const input = document.getElementById('rename-file-input');
   1064                 if (input) {
   1065                     input.focus();
   1066                     input.select();
   1067                 }
   1068             }, 100);
   1069         }
   1070     },
   1071 
   1072     hideModal: () => {
   1073         if (!UI.activeModal) return;
   1074         const overlay = document.getElementById('modal-overlay');
   1075         const modal = UI.activeModal;
   1076         modal.classList.remove('modal-enter');
   1077         modal.classList.add('modal-exit');
   1078         setTimeout(() => {
   1079             modal.classList.add('hidden');
   1080             overlay.classList.add('hidden');
   1081             overlay.classList.remove('flex');
   1082             UI.activeModal = null;
   1083         }, 200);
   1084     },
   1085 
   1086     toast: (msg, type = 'info') => {
   1087         const container = document.getElementById('toast-container');
   1088         const toast = document.createElement('div');
   1089         const colors = type === 'error' ? 'bg-red-500/90 text-white border-red-400' : 'bg-veyrix-surface text-white border-veyrix-border';
   1090         toast.className = `px-4 py-2 rounded-lg border shadow-lg text-sm font-medium transform transition-all duration-300 translate-y-10 opacity-0 flex items-center gap-2 ${colors}`;
   1091         toast.innerHTML = type === 'error' ? `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> ${msg}` 
   1092                                            : `<svg class="w-4 h-4 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg> ${msg}`;
   1093         container.appendChild(toast);
   1094         
   1095         requestAnimationFrame(() => {
   1096             toast.classList.remove('translate-y-10', 'opacity-0');
   1097         });
   1098 
   1099         setTimeout(() => {
   1100             toast.classList.add('translate-y-10', 'opacity-0');
   1101             setTimeout(() => toast.remove(), 300);
   1102         }, 3000);
   1103     },
   1104 
   1105     renderSidebar: () => {
   1106         const list = document.getElementById('file-list');
   1107         list.innerHTML = '';
   1108         state.files.sort((a,b) => b.lastModified - a.lastModified).forEach(file => {
   1109             const isActive = file.id === state.activeFileId;
   1110             const el = document.createElement('div');
   1111             el.className = `flex items-center justify-between p-1 px-1.5 rounded-lg cursor-pointer group transition-colors ${isActive ? 'bg-veyrix-accent/20 text-white' : 'text-veyrix-text hover:bg-white/5 hover:text-white'}`;
   1112             el.innerHTML = `
   1113                 <div class="flex items-center gap-2 overflow-hidden flex-1 py-1 px-1" onclick="App.openFile('${file.id}')" role="button" tabindex="0">
   1114                     ${Utils.getFileIcon(file.ext, file.name)}
   1115                     <span class="text-sm truncate select-none">${Utils.getDisplayName(file)}</span>
   1116                     ${file.unsaved ? `<div class="w-1.5 h-1.5 rounded-full bg-veyrix-accent shrink-0 ml-1"></div>` : ''}
   1117                 </div>
   1118                 <div class="relative dd-trigger">
   1119                     <button onclick="UI.toggleFileDropdown('${file.id}', this, event)" class="p-1 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors" title="Options">
   1120                         <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"></path></svg>
   1121                     </button>
   1122                 </div>
   1123             `;
   1124             list.appendChild(el);
   1125         });
   1126     },
   1127 
   1128     updateTopBar: () => {
   1129         const file = state.files.find(f => f.id === state.activeFileId);
   1130         const placeholder = document.getElementById('editor-placeholder');
   1131         const activeFileContainer = document.getElementById('top-active-file');
   1132         const nameEl = document.getElementById('top-file-name');
   1133         const unsavedEl = document.getElementById('top-file-unsaved');
   1134 
   1135         if (file) {
   1136             placeholder.classList.add('hidden');
   1137             activeFileContainer.classList.remove('hidden');
   1138             activeFileContainer.classList.add('flex');
   1139             
   1140             let displayName = Utils.getDisplayName(file);
   1141             if (window.innerWidth <= 768) {
   1142                 displayName = Utils.getMobileShortName(file.name, file.ext);
   1143             }
   1144             
   1145             nameEl.textContent = displayName;
   1146             unsavedEl.classList.toggle('hidden', !file.unsaved);
   1147         } else {
   1148             placeholder.classList.remove('hidden');
   1149             activeFileContainer.classList.add('hidden');
   1150             activeFileContainer.classList.remove('flex');
   1151             nameEl.textContent = 'No file open';
   1152             unsavedEl.classList.add('hidden');
   1153             if(state.editor) {
   1154                 state.editor.setValue('', -1);
   1155                 state.editor.setReadOnly(true);
   1156             }
   1157         }
   1158     },
   1159 
   1160     switchShareTab: (tab) => {
   1161         document.getElementById('tab-local').classList.toggle('hidden', tab !== 'local');
   1162         document.getElementById('tab-cloud').classList.toggle('hidden', tab !== 'cloud');
   1163         
   1164         document.getElementById('tab-btn-local').className = `flex-1 py-3 text-sm font-medium border-b-2 transition-colors ${tab === 'local' ? 'border-veyrix-accent text-veyrix-accent' : 'border-transparent text-veyrix-muted hover:text-white'}`;
   1165         document.getElementById('tab-btn-cloud').className = `flex-1 py-3 text-sm font-medium border-b-2 transition-colors ${tab === 'cloud' ? 'border-veyrix-accent text-veyrix-accent' : 'border-transparent text-veyrix-muted hover:text-white'}`;
   1166     },
   1167 
   1168     copyToClipboard: (elementId) => {
   1169         const el = document.getElementById(elementId);
   1170         el.select();
   1171         document.execCommand('copy');
   1172         UI.toast('Link copied to clipboard');
   1173     }
   1174 };
   1175 
   1176 // Cloud & Firebase Features
   1177 const Cloud = {
   1178     firebaseLoaded: false,
   1179     loadFirebase: async () => {
   1180         if (Cloud.firebaseLoaded) return true;
   1181         try {
   1182             const [app, auth, firestore] = await Promise.all([
   1183                 import('https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js'),
   1184                 import('https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js'),
   1185                 import('https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js')
   1186             ]);
   1187             
   1188             const fbApp = app.initializeApp(firebaseConfig);
   1189             const fbAuth = auth.getAuth(fbApp);
   1190             const fbDb = firestore.getFirestore(fbApp);
   1191             
   1192             await auth.signInAnonymously(fbAuth);
   1193             
   1194             window.fbDb = fbDb;
   1195             window.fbFirestore = firestore;
   1196             Cloud.firebaseLoaded = true;
   1197             return true;
   1198         } catch (e) {
   1199             console.error(e);
   1200             UI.toast('Failed to initialize secure cloud share', 'error');
   1201             return false;
   1202         }
   1203     },
   1204     generateLocalShare: () => {
   1205         const file = state.files.find(f => f.id === state.activeFileId);
   1206         if(!file) return UI.toast('No file to share', 'error');
   1207         
   1208         const compressed = LZString.compressToEncodedURIComponent(file.content);
   1209         const url = `${window.location.origin}${window.location.pathname}?ext=${file.ext}&code=${compressed}`;
   1210         
   1211         const resDiv = document.getElementById('local-share-result');
   1212         const urlInput = document.getElementById('local-share-url');
   1213         urlInput.value = url;
   1214         resDiv.classList.remove('hidden');
   1215     },
   1216     generateCloudShare: async () => {
   1217         const file = state.files.find(f => f.id === state.activeFileId);
   1218         if(!file) return UI.toast('No file to share', 'error');
   1219 
   1220         const btn = document.getElementById('btn-cloud-share');
   1221         btn.innerHTML = `<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Uploading...`;
   1222         btn.disabled = true;
   1223 
   1224         if(await Cloud.loadFirebase()) {
   1225             try {
   1226                 const pass = document.getElementById('cloud-password').value;
   1227                 const passHash = await Utils.hashPassword(pass);
   1228                 
   1229                 const data = {
   1230                     content: file.content,
   1231                     ext: file.ext,
   1232                     name: file.name,
   1233                     timestamp: window.fbFirestore.serverTimestamp(),
   1234                     protected: !!passHash,
   1235                     hash: passHash
   1236                 };
   1237 
   1238                 const docRef = await window.fbFirestore.addDoc(window.fbFirestore.collection(window.fbDb, "shared_snippets"), data);
   1239                 
   1240                 const url = `${window.location.origin}${window.location.pathname}?id=${docRef.id}`;
   1241                 const resDiv = document.getElementById('cloud-share-result');
   1242                 const urlInput = document.getElementById('cloud-share-url');
   1243                 urlInput.value = url;
   1244                 resDiv.classList.remove('hidden');
   1245                 UI.toast('Uploaded to secure cloud');
   1246             } catch(e) {
   1247                 console.error(e);
   1248                 UI.toast('Upload failed', 'error');
   1249             }
   1250         }
   1251         
   1252         btn.innerHTML = `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg> Upload & Share`;
   1253         btn.disabled = false;
   1254     },
   1255     currentCloudDoc: null,
   1256     handleCloudLoad: async (id) => {
   1257         UI.toast('Loading from cloud...');
   1258         if(await Cloud.loadFirebase()) {
   1259             try {
   1260                 const docRef = window.fbFirestore.doc(window.fbDb, "shared_snippets", id);
   1261                 const docSnap = await window.fbFirestore.getDoc(docRef);
   1262                 
   1263                 if (docSnap.exists()) {
   1264                     const data = docSnap.data();
   1265                     if(data.protected) {
   1266                         Cloud.currentCloudDoc = data;
   1267                         UI.showModal('password-modal');
   1268                     } else {
   1269                         App.loadExternalContent(data.name || 'shared', data.ext, data.content);
   1270                     }
   1271                 } else {
   1272                     UI.toast('Cloud file not found', 'error');
   1273                 }
   1274             } catch(e) {
   1275                 console.error(e);
   1276                 UI.toast('Failed to load cloud file', 'error');
   1277             }
   1278         }
   1279     },
   1280     verifyAndLoadCloudFile: async () => {
   1281         const pass = document.getElementById('access-password').value;
   1282         const hash = await Utils.hashPassword(pass);
   1283         if(hash === Cloud.currentCloudDoc.hash) {
   1284             UI.hideModal();
   1285             App.loadExternalContent(Cloud.currentCloudDoc.name || 'shared', Cloud.currentCloudDoc.ext, Cloud.currentCloudDoc.content);
   1286             Cloud.currentCloudDoc = null;
   1287             document.getElementById('access-password').value = '';
   1288         } else {
   1289             UI.toast('Incorrect password', 'error');
   1290         }
   1291     },
   1292     nativeShare: async () => {
   1293         const file = state.files.find(f => f.id === state.activeFileId);
   1294         if(!file) return UI.toast('No file to share', 'error');
   1295         try {
   1296             await navigator.share({
   1297                 title: Utils.getDisplayName(file),
   1298                 text: file.content,
   1299                 url: window.location.href
   1300             });
   1301         } catch (err) {
   1302             console.log('Share canceled or failed');
   1303         }
   1304     }
   1305 };
   1306 
   1307 // Core Application Logic
   1308 const App = {
   1309     init: async () => {
   1310         // Init Ace Editor
   1311         state.editor = ace.edit("editor");
   1312         state.editor.setTheme("ace/theme/tomorrow_night_eighties");
   1313         state.editor.setOptions({
   1314             fontSize: "14px",
   1315             showPrintMargin: false,
   1316             wrap: true,
   1317             enableBasicAutocompletion: true,
   1318             enableLiveAutocompletion: true,
   1319             fontFamily: "'Fira Code', 'Consolas', monospace"
   1320         });
   1321         
   1322         // Exact viewport fix for mobile keyboards
   1323         if (window.visualViewport) {
   1324             const adjustViewport = () => {
   1325                 const vh = window.visualViewport.height;
   1326                 const headerHeight = document.querySelector('header').offsetHeight;
   1327                 
   1328                 // Pin body to actual visual height
   1329                 document.body.style.height = vh + 'px';
   1330                 
   1331                 // Allow exact remainder height to the main workspace
   1332                 const mainArea = document.getElementById('main-workspace');
   1333                 if (mainArea) {
   1334                     mainArea.style.height = (vh - headerHeight) + 'px';
   1335                 }
   1336                 
   1337                 if (state.editor) {
   1338                     state.editor.resize();
   1339                 }
   1340             };
   1341             window.visualViewport.addEventListener('resize', adjustViewport);
   1342             adjustViewport();
   1343         }
   1344 
   1345         // Set version displays
   1346         const versionDisplay = document.getElementById('app-version-display');
   1347         if (versionDisplay) versionDisplay.textContent = `v${APP_VERSION}`;
   1348         
   1349         const settingsVersionDisplay = document.getElementById('settings-version-display');
   1350         if (settingsVersionDisplay) settingsVersionDisplay.textContent = `Version: v${APP_VERSION}`;
   1351 
   1352         // Check version for onboarding modal
   1353         const storedVersion = localStorage.getItem('veyrix_version');
   1354         if (storedVersion !== APP_VERSION) {
   1355             setTimeout(() => {
   1356                 UI.showModal('help-modal');
   1357                 localStorage.setItem('veyrix_version', APP_VERSION);
   1358             }, 500); // Small delay to let the UI finish rendering first
   1359         }
   1360 
   1361         // Feature Detect Native Share
   1362         if (navigator.share) {
   1363             const nativeShareSection = document.getElementById('native-share-section');
   1364             if(nativeShareSection) {
   1365                 nativeShareSection.classList.remove('hidden');
   1366             }
   1367         }
   1368 
   1369         // --- Dot-Space Auto Fix Implementation ---
   1370         const handleDotSpace = (e) => {
   1371             const el = e.target;
   1372             const val = el.value;
   1373             const selStart = el.selectionStart;
   1374 
   1375             // Detect if ". " exists exactly right before the cursor
   1376             if (selStart >= 2 && val.substring(selStart - 2, selStart) === '. ') {
   1377                 el.value = val.substring(0, selStart - 1) + val.substring(selStart);
   1378                 el.setSelectionRange(selStart - 1, selStart - 1);
   1379             }
   1380         };
   1381 
   1382         const newFileInput = document.getElementById('new-file-input');
   1383         if(newFileInput) newFileInput.addEventListener('input', handleDotSpace);
   1384 
   1385         const renameFileInput = document.getElementById('rename-file-input');
   1386         if(renameFileInput) renameFileInput.addEventListener('input', handleDotSpace);
   1387 
   1388         // --- Enter Key Listeners for Modals ---
   1389         newFileInput.addEventListener('keydown', (e) => {
   1390             if (e.key === 'Enter') {
   1391                 e.preventDefault();
   1392                 App.createNewFile();
   1393             }
   1394         });
   1395 
   1396         renameFileInput.addEventListener('keydown', (e) => {
   1397             if (e.key === 'Enter') {
   1398                 e.preventDefault();
   1399                 App.executeRename();
   1400             }
   1401         });
   1402 
   1403         // Global Event Delegation for Clicks (Closes sidebars & dropdowns)
   1404         document.addEventListener('click', (e) => {
   1405             // Dropdowns
   1406             if (!e.target.closest('#global-file-dropdown') && !e.target.closest('.dd-trigger')) {
   1407                 UI.closeAllDropdowns();
   1408             }
   1409 
   1410             // Outside Sidebar Click
   1411             const sidebar = document.getElementById('sidebar');
   1412             const toggleBtn = e.target.closest('.sidebar-toggle-btn');
   1413             const isSidebarClick = e.target.closest('#sidebar');
   1414             const isBackdropClick = e.target.id === 'sidebar-backdrop';
   1415             
   1416             if (!sidebar.classList.contains('sidebar-collapsed')) {
   1417                 // If it's a mobile backdrop click, OR an external desktop click
   1418                 if (isBackdropClick || (!isSidebarClick && !toggleBtn)) {
   1419                     UI.toggleSidebar(true);
   1420                 }
   1421             }
   1422         });
   1423 
   1424         // Window resize listeners
   1425         window.addEventListener('resize', Utils.debounce(() => {
   1426             UI.updateTopBar();
   1427         }, 200));
   1428 
   1429         // Swipe to open/close sidebar
   1430         let touchStartX = 0;
   1431         let touchStartY = 0;
   1432         document.addEventListener('touchstart', e => {
   1433             touchStartX = e.changedTouches[0].screenX;
   1434             touchStartY = e.changedTouches[0].screenY;
   1435         }, {passive: true});
   1436         
   1437         document.addEventListener('touchend', e => {
   1438             let touchEndX = e.changedTouches[0].screenX;
   1439             let touchEndY = e.changedTouches[0].screenY;
   1440             if (Math.abs(touchEndX - touchStartX) > Math.abs(touchEndY - touchStartY)) {
   1441                 if (touchEndX - touchStartX > 80 && touchStartX < 50) {
   1442                     const sb = document.getElementById('sidebar');
   1443                     if (sb.classList.contains('sidebar-collapsed')) UI.toggleSidebar(); // open
   1444                 } else if (touchStartX - touchEndX > 80) {
   1445                     const sb = document.getElementById('sidebar');
   1446                     if (!sb.classList.contains('sidebar-collapsed')) UI.toggleSidebar(true); // close
   1447                 }
   1448             }
   1449         }, {passive: true});
   1450         
   1451         state.editor.on("change", Utils.debounce(() => {
   1452             if(state.activeFileId) {
   1453                 const file = state.files.find(f => f.id === state.activeFileId);
   1454                 if(file && file.content !== state.editor.getValue()) {
   1455                     file.content = state.editor.getValue();
   1456                     if(!file.unsaved) {
   1457                         file.unsaved = true;
   1458                         UI.renderSidebar();
   1459                         UI.updateTopBar();
   1460                     }
   1461                 }
   1462             }
   1463         }, 500));
   1464 
   1465         // Close modals on overlay click
   1466         document.getElementById('modal-overlay').addEventListener('click', (e) => {
   1467             if(e.target.id === 'modal-overlay') UI.hideModal();
   1468         });
   1469 
   1470         // Keyboard Shortcuts
   1471         window.addEventListener('keydown', (e) => {
   1472             if (e.ctrlKey && e.key === 's') { e.preventDefault(); App.saveCurrentFile(); }
   1473             if (e.ctrlKey && e.key === 'o') { e.preventDefault(); document.getElementById('file-upload').click(); }
   1474             if (e.altKey && e.shiftKey && e.key.toLowerCase() === 'f') { e.preventDefault(); App.formatCode(); }
   1475             if (e.key === 'Escape') { 
   1476                 UI.hideModal(); 
   1477                 UI.closeAllDropdowns(); 
   1478                 const sb = document.getElementById('sidebar');
   1479                 if (sb && !sb.classList.contains('sidebar-collapsed')) {
   1480                     UI.toggleSidebar(true);
   1481                 }
   1482             }
   1483         });
   1484 
   1485         // Init DB & State Setup
   1486         try {
   1487             await DB.init();
   1488             state.files = await DB.getAll();
   1489             
   1490             // Apply Initial Sidebar State
   1491             const savedState = localStorage.getItem('veyrix_sidebar');
   1492             const sb = document.getElementById('sidebar');
   1493             const hamburger = document.querySelector('.hamburger');
   1494             if (savedState === 'closed') {
   1495                 sb.classList.add('sidebar-collapsed');
   1496                 if(hamburger) { hamburger.classList.remove('is-active'); hamburger.setAttribute('aria-expanded', 'false'); }
   1497             } else if (savedState === 'open') {
   1498                 sb.classList.remove('sidebar-collapsed');
   1499                 if(hamburger) { hamburger.classList.add('is-active'); hamburger.setAttribute('aria-expanded', 'true'); }
   1500             } else {
   1501                 // Default fallback: closed on mobile, open on desktop
   1502                 if (window.innerWidth <= 768) {
   1503                     sb.classList.add('sidebar-collapsed');
   1504                     if(hamburger) { hamburger.classList.remove('is-active'); hamburger.setAttribute('aria-expanded', 'false'); }
   1505                 } else {
   1506                     sb.classList.remove('sidebar-collapsed');
   1507                     if(hamburger) { hamburger.classList.add('is-active'); hamburger.setAttribute('aria-expanded', 'true'); }
   1508                 }
   1509             }
   1510 
   1511             UI.renderSidebar();
   1512             
   1513             const lastActive = localStorage.getItem('veyrix_active');
   1514             if(lastActive && state.files.find(f => f.id === lastActive)) {
   1515                 App.openFile(lastActive);
   1516             } else {
   1517                 UI.updateTopBar();
   1518             }
   1519         } catch(e) {
   1520             UI.toast('Failed to load local DB', 'error');
   1521         }
   1522 
   1523         // Handle URL Parameters (Sharing)
   1524         const params = new URLSearchParams(window.location.search);
   1525         if(params.has('ext') && params.has('code')) {
   1526             const ext = params.get('ext');
   1527             const code = LZString.decompressFromEncodedURIComponent(params.get('code'));
   1528             if(code !== null) {
   1529                 App.loadExternalContent('shared_snippet', ext, code);
   1530             }
   1531         } else if(params.has('id')) {
   1532             Cloud.handleCloudLoad(params.get('id'));
   1533         }
   1534     },
   1535 
   1536     installApp: async () => {
   1537         if (deferredPrompt) {
   1538             deferredPrompt.prompt();
   1539             const { outcome } = await deferredPrompt.userChoice;
   1540             if (outcome === 'accepted') {
   1541                 deferredPrompt = null;
   1542                 const btn = document.getElementById('install-app-btn');
   1543                 btn.classList.add('opacity-50', 'cursor-not-allowed');
   1544                 btn.disabled = true;
   1545             }
   1546         }
   1547     },
   1548     
   1549     createNewFile: async () => {
   1550         const input = document.getElementById('new-file-input').value.trim();
   1551         if(!input) return UI.toast('Filename cannot be empty', 'error');
   1552         
   1553         let name = input;
   1554         let ext = '';
   1555         
   1556         const lastDotIdx = input.lastIndexOf('.');
   1557         if (lastDotIdx > 0 && lastDotIdx < input.length - 1) {
   1558             ext = input.substring(lastDotIdx + 1);
   1559             name = input.substring(0, lastDotIdx);
   1560         } else if (lastDotIdx === 0) {
   1561             name = input;
   1562             ext = '';
   1563         } else if (lastDotIdx === input.length - 1) {
   1564             name = input.substring(0, lastDotIdx);
   1565             ext = '';
   1566         }
   1567 
   1568         const newFile = {
   1569             id: Utils.generateId(),
   1570             name,
   1571             ext,
   1572             content: '',
   1573             unsaved: true
   1574         };
   1575 
   1576         state.files.push(newFile);
   1577         await DB.save(newFile);
   1578         UI.hideModal();
   1579         document.getElementById('new-file-input').value = '';
   1580         App.openFile(newFile.id);
   1581         UI.toast('File created');
   1582     },
   1583 
   1584     openFile: (id) => {
   1585         const file = state.files.find(f => f.id === id);
   1586         if(!file) return;
   1587 
   1588         state.activeFileId = id;
   1589         localStorage.setItem('veyrix_active', id);
   1590         
   1591         state.editor.setReadOnly(false);
   1592         state.editor.setValue(file.content, -1);
   1593         
   1594         const mode = Utils.getModeFromExt(file.ext, file.name);
   1595         state.editor.session.setMode(`ace/mode/${mode}`);
   1596         
   1597         UI.renderSidebar();
   1598         UI.updateTopBar();
   1599         
   1600         if(window.innerWidth <= 768) {
   1601             UI.toggleSidebar(true); // force close on mobile selection
   1602         }
   1603     },
   1604 
   1605     saveCurrentFile: async () => {
   1606         if(!state.activeFileId) return;
   1607         const file = state.files.find(f => f.id === state.activeFileId);
   1608         if(!file) return;
   1609 
   1610         file.content = state.editor.getValue();
   1611         file.unsaved = false;
   1612         await DB.save(file);
   1613         
   1614         UI.renderSidebar();
   1615         UI.updateTopBar();
   1616         UI.toast('File saved');
   1617     },
   1618 
   1619     triggerDownload: async (id = null, e = null) => {
   1620         if(e) e.stopPropagation();
   1621         UI.closeAllDropdowns();
   1622         
   1623         const fileId = id || state.activeFileId;
   1624         if(!fileId) return UI.toast('No file to download', 'error');
   1625         
   1626         const file = state.files.find(f => f.id === fileId);
   1627         if(!file) return;
   1628 
   1629         const content = file.id === state.activeFileId ? state.editor.getValue() : file.content;
   1630         const fileName = Utils.getDisplayName(file);
   1631         
   1632         if (navigator.canShare) {
   1633             try {
   1634                 const fileObj = new File([content], fileName, { type: 'application/octet-stream' });
   1635                 if (navigator.canShare({ files: [fileObj] })) {
   1636                     await navigator.share({
   1637                         files: [fileObj],
   1638                         title: fileName
   1639                     });
   1640                     return;
   1641                 }
   1642             } catch (err) {
   1643                 console.log('Share canceled or failed, falling back to native download', err);
   1644             }
   1645         }
   1646 
   1647         const blob = new Blob([content], { type: 'application/octet-stream' });
   1648         const url = URL.createObjectURL(blob);
   1649         
   1650         const a = document.createElement('a');
   1651         a.href = url;
   1652         a.download = fileName;
   1653         document.body.appendChild(a);
   1654         a.click();
   1655         document.body.removeChild(a);
   1656         URL.revokeObjectURL(url);
   1657         
   1658         UI.toast('Download started');
   1659     },
   1660 
   1661     triggerSnapshot: () => {
   1662         if(!state.activeFileId) return UI.toast('No file open', 'error');
   1663         const file = state.files.find(f => f.id === state.activeFileId);
   1664         if(!file) return;
   1665 
   1666         file.snapshots = file.snapshots || [];
   1667         if(file.snapshots.length >= 5) {
   1668             const oldest = file.snapshots[0];
   1669             const date = new Date(oldest.ts).toLocaleString();
   1670             document.getElementById('snapshot-limit-oldest-info').textContent = `Version ${oldest.version || 1} - ${date}`;
   1671             UI.showModal('snapshot-limit-modal');
   1672         } else {
   1673             App.executeSnapshotSave(false);
   1674         }
   1675     },
   1676 
   1677     executeSnapshotSave: async (replaceOldest = false) => {
   1678         const file = state.files.find(f => f.id === state.activeFileId);
   1679         if(!file) return;
   1680 
   1681         file.snapshots = file.snapshots || [];
   1682         file.snapshotCounter = (file.snapshotCounter || file.snapshots.length) + 1;
   1683 
   1684         if (replaceOldest) {
   1685             file.snapshots.shift();
   1686             UI.hideModal();
   1687         }
   1688 
   1689         file.snapshots.push({
   1690             ts: Date.now(),
   1691             content: state.editor.getValue(),
   1692             version: file.snapshotCounter
   1693         });
   1694         
   1695         await DB.save(file);
   1696         UI.toast('Snapshot saved to history');
   1697     },
   1698 
   1699     triggerRestore: (index) => {
   1700         UI.showConfirm(
   1701             "Restore Snapshot", 
   1702             "Are you sure you want to restore this snapshot? Current unsaved changes will be lost.", 
   1703             "Restore", 
   1704             () => App.restoreSnapshot(index)
   1705         );
   1706     },
   1707 
   1708     restoreSnapshot: async (index) => {
   1709         const file = state.files.find(f => f.id === state.activeFileId);
   1710         if(!file || !file.snapshots || !file.snapshots[index]) return;
   1711         
   1712         state.editor.setValue(file.snapshots[index].content, -1);
   1713         UI.toast('Restored previous version');
   1714         App.saveCurrentFile(); // auto save after restore
   1715     },
   1716 
   1717     triggerRename: async (id, e) => {
   1718         if(e) e.stopPropagation();
   1719         UI.closeAllDropdowns();
   1720         const file = state.files.find(f => f.id === id);
   1721         if(!file) return;
   1722 
   1723         state.renameTargetId = id;
   1724         const input = document.getElementById('rename-file-input');
   1725         input.value = Utils.getDisplayName(file);
   1726         UI.showModal('rename-file-modal');
   1727     },
   1728 
   1729     executeRename: async () => {
   1730         const id = state.renameTargetId;
   1731         const file = state.files.find(f => f.id === id);
   1732         if(!file) return;
   1733 
   1734         const input = document.getElementById('rename-file-input').value.trim();
   1735         if(!input) return UI.toast('Filename cannot be empty', 'error');
   1736 
   1737         const lastDotIdx = input.lastIndexOf('.');
   1738         
   1739         if (lastDotIdx > 0 && lastDotIdx < input.length - 1) {
   1740             file.ext = input.substring(lastDotIdx + 1);
   1741             file.name = input.substring(0, lastDotIdx);
   1742         } else if (lastDotIdx === 0) {
   1743             file.name = input;
   1744             file.ext = '';
   1745         } else if (lastDotIdx === input.length - 1) {
   1746             file.name = input.substring(0, lastDotIdx);
   1747             file.ext = '';
   1748         } else {
   1749             file.name = input;
   1750             file.ext = '';
   1751         }
   1752         
   1753         await DB.save(file);
   1754         UI.hideModal();
   1755         UI.renderSidebar();
   1756         UI.updateTopBar();
   1757         UI.toast('File renamed');
   1758     },
   1759 
   1760     triggerShare: async (id, e) => {
   1761         if(e) e.stopPropagation();
   1762         UI.closeAllDropdowns();
   1763         App.openFile(id);
   1764         UI.showModal('share-modal');
   1765     },
   1766 
   1767     triggerDelete: async (id, e) => {
   1768         if(e) e.stopPropagation();
   1769         UI.closeAllDropdowns();
   1770         
   1771         UI.showConfirm(
   1772             "Delete File", 
   1773             "Are you sure you want to delete this file? This action cannot be undone.", 
   1774             "Delete", 
   1775             async () => {
   1776                 await DB.delete(id);
   1777                 state.files = state.files.filter(f => f.id !== id);
   1778                 if(state.activeFileId === id) {
   1779                     state.activeFileId = null;
   1780                     localStorage.removeItem('veyrix_active');
   1781                     UI.updateTopBar();
   1782                 }
   1783                 UI.renderSidebar();
   1784                 UI.toast('File deleted');
   1785             }
   1786         );
   1787     },
   1788 
   1789     handleFileUpload: (e) => {
   1790         const file = e.target.files[0];
   1791         if(!file) return;
   1792 
   1793         // Validation for common non-text binaries (mobile native pickers might circumvent accept filter)
   1794         if(file.type.startsWith('image/') || file.type.startsWith('video/') || file.type.startsWith('audio/') || file.type === 'application/pdf' || file.name.match(/\.(exe|dll|bin|zip|rar|tar|gz|7z|apk|dmg|iso)$/i)) {
   1795             e.target.value = '';
   1796             return UI.toast('Invalid file type (Text/Code only)', 'error');
   1797         }
   1798 
   1799         const reader = new FileReader();
   1800         reader.onload = async (ev) => {
   1801             const input = file.name;
   1802             let name = input;
   1803             let ext = '';
   1804             
   1805             const lastDotIdx = input.lastIndexOf('.');
   1806             if (lastDotIdx > 0 && lastDotIdx < input.length - 1) {
   1807                 ext = input.substring(lastDotIdx + 1);
   1808                 name = input.substring(0, lastDotIdx);
   1809             } else if (lastDotIdx === 0) {
   1810                 name = input;
   1811                 ext = '';
   1812             } else if (lastDotIdx === input.length - 1) {
   1813                 name = input.substring(0, lastDotIdx);
   1814                 ext = '';
   1815             }
   1816 
   1817             const newFile = {
   1818                 id: Utils.generateId(),
   1819                 name,
   1820                 ext,
   1821                 content: ev.target.result,
   1822                 unsaved: false
   1823             };
   1824 
   1825             state.files.push(newFile);
   1826             await DB.save(newFile);
   1827             App.openFile(newFile.id);
   1828             UI.toast('File imported');
   1829         };
   1830         reader.readAsText(file);
   1831         e.target.value = ''; // reset
   1832     },
   1833 
   1834     loadExternalContent: (name, ext, content) => {
   1835         // Creates a temporary file that won't auto-save to DB unless specifically saved
   1836         const file = {
   1837             id: 'temp_' + Date.now(),
   1838             name,
   1839             ext,
   1840             content,
   1841             unsaved: true // implies it needs saving
   1842         };
   1843         state.files.push(file);
   1844         App.openFile(file.id);
   1845         document.getElementById('banner-warning').classList.remove('hidden');
   1846         window.history.replaceState({}, document.title, window.location.pathname);
   1847     },
   1848 
   1849     formatCode: () => {
   1850         if(!state.activeFileId) return;
   1851         const file = state.files.find(f => f.id === state.activeFileId);
   1852         if(!file) return;
   1853         
   1854         try {
   1855             const beautify = ace.require("ace/ext/beautify");
   1856             beautify.beautify(state.editor.session);
   1857             UI.toast('Document formatted');
   1858         } catch(e) {
   1859             UI.toast('Formatting not supported for this language', 'error');
   1860         }
   1861     },
   1862 
   1863     toggleFullscreen: () => {
   1864         if (!document.fullscreenElement) {
   1865             document.documentElement.requestFullscreen().catch(() => UI.toast('Fullscreen failed', 'error'));
   1866         } else {
   1867             if (document.exitFullscreen) document.exitFullscreen();
   1868         }
   1869     },
   1870 
   1871     factoryReset: async () => {
   1872         if(confirm('Are you sure? This will delete all local files and cannot be undone.')) {
   1873             const request = indexedDB.deleteDatabase('VeyrixFS');
   1874             request.onsuccess = () => {
   1875                 localStorage.clear();
   1876                 window.location.reload();
   1877             };
   1878         }
   1879     }
   1880 };
   1881 
   1882 // Start application
   1883 window.onload = App.init;
   1884 
   1885 // Register Service Worker for PWA
   1886 if ('serviceWorker' in navigator) {
   1887     window.addEventListener('load', () => {
   1888         navigator.serviceWorker.register('assets/sw.js').catch(err => {
   1889             console.log('SW registration failed: ', err);
   1890         });
   1891     });
   1892 }
   1893 </script>
   1894 </body>
   1895 
   1896 </html>
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror